#include "tools.hpp"
#include "rainbow.hpp"

namespace eosio {
	uint64_t rainbow::login( account_name name, account_name referrer ) {
		auto user_index = user_table.get_index<N(byname)>();
		auto itr = user_index.find( name );

		uint64_t userid = 0;
		if( itr == user_index.end() ) {
			uint64_t referrer_id = 0;
			if( is_account(referrer) ) {
				if(referrer != _self) {
					referrer_id = getUserId( referrer );
				}
			}

			extended_asset zeroEos(0,S(4,EOS));
			extended_asset zeroRab(0,S(4,RAB));
			user_table.emplace( _self, [&](auto &result ) {
				result.id              = user_table.available_primary_key();
				result.name            = name;
				result.regtime         = now();
				result.lastlogin	   = now();
				result.referrer_id     = referrer_id;
				result.totalbet        = zeroEos;
				result.rewarded_amount = zeroEos;
				result.pledgerab       = zeroRab;
				result.dividend        = zeroEos;
				result.failcount       = 0;
				if(result.id == 0) {
					result.id = 1;
				}

				userid = result.id;
			});
		}
		else {
			if( name != _self ) {
				eosio_assert( itr->failcount < 3, "101" );
			}
			userid = itr->id;
			user_index.modify( itr, _self, [&](auto &result ) {
				result.lastlogin = now();
            });
		}

		return userid;
	}

	//账号获取ID
	uint64_t rainbow::getUserId( account_name name ) {
		auto user_index = user_table.get_index<N(byname)>();
		auto itr = user_index.find( name );
		if( itr == user_index.end() ) {
			return 0;
		}
		else {
			return itr->id;
		}
	}

	//ID获取账号
	account_name rainbow::getUserName( uint64_t userid ) {
		auto itr = user_table.find( userid );
		return itr->name;
	}

	//游戏配置
	void rainbow::setup( uint8_t gameswitch, uint8_t canraise ) {
		require_auth( _self );
		auto itr = other_table.begin();
		if( itr == other_table.end() ) {
			other_table.emplace( _self, [&](auto &result ) {
				result.id = other_table.available_primary_key();
				result.isopen = gameswitch;
				result.updatetime = now();
			});
		}
		else {
			other_table.modify( itr, _self, [&](auto &result ) {
                result.isopen = gameswitch;
				result.updatetime = now();
            });
		}

		itr = other_table.begin();
		if( itr == other_table.end() ) {
			other_table.emplace( _self, [&](auto &result ) {
				result.id = other_table.available_primary_key();
				result.canraise = canraise;
				result.updatetime = now();
			});
		}
		else {
			other_table.modify( itr, _self, [&](auto &result ) {
                result.canraise = canraise;
				result.updatetime = now();
            });
		}

		car->getNum( 3 );
	}

	//判断游戏开关状态和用户是否被设置为黑名单
	void rainbow::check() {
		auto itr = other_table.begin();
		uint8_t isopen = 0;
		if( itr != other_table.end() ) {
			isopen = itr->isopen;
		}

		eosio_assert( isopen == 1, "100" );
	}

	//游戏结算(数据层面，未执行奖励转账)
	void rainbow::settlement( uint8_t gametype, uint64_t gameid ) {
		if( gametype == 1 ) {
			car->settleGame( gameid );
		}
	}

	//追加到分红池
	void rainbow::addDividend( asset quantity ) {
		uint64_t dividend = ceil( (quantity.amount / 100) * 11 );
		time updatetime = now();
		auto itr = other_table.begin();
		if( itr == other_table.end() ) {
			other_table.emplace( _self, [&](auto &result ) {
				extended_asset dividendEos( dividend,S(4,EOS) );
				result.id = other_table.available_primary_key();
				result.amount = dividendEos;
				result.updatetime = updatetime;
			});
		}
		else {
			other_table.modify( itr, _self, [&](auto &result ) {
				extended_asset dividendEos( itr->amount.amount + dividend,S(4,EOS) );
                result.amount = dividendEos;
				result.updatetime = updatetime;
            });
		}
	}

	//转账给玩家
	void rainbow::issue( account_name account, asset quantity, string memo ) {
		if( quantity.symbol == S(4,EOS) ) {
			action(
				permission_level{ _self, N(active) },
				N(eosio.token), N(transfer),
				std::make_tuple( _self, account, quantity, std::string(memo) )
			).send();
		}
		else if( quantity.symbol == S(4,RAB) ) {
			action(
				permission_level{_self, N(active)},
				config::tokenContract, N(transfer),
				std::make_tuple( _self, account, quantity, std::string(memo) )
			).send();
		}
	}

	//是否还在筹募中
	bool rainbow::isCanRaise() {
		auto itr = other_table.begin();
		if( itr == other_table.end() ) {
			return false;
		}
		else {
            if ( itr->canraise == 0 ) {
				return false;
			}
			else {
				return true;
			}
		}
	}

	//获取兑换的RAB金额
	asset rainbow::getExchangeRab( asset quantity, asset lastTotal ) {
		uint64_t big   = 0;
		uint64_t small = 0;
              
		if( lastTotal.amount + quantity.amount > config::downLevel and lastTotal.amount < config::downLevel ) {
			if( lastTotal.amount > 0 ) {
				big   = config::downLevel - lastTotal.amount;
				small = quantity.amount - big;
			}
			else {
				small = (lastTotal.amount + quantity.amount) - config::downLevel;
				big   = quantity.amount - small;
			}
		}
		else {
			big   = quantity.amount;
			small = 0;
		}

		extended_asset token_amount((big * 500) + (small * 400),S(4,RAB));
		return token_amount;
	}
	
	//挖矿奖励
	void rainbow::mining( asset quantity, uint64_t userid ) {
		uint64_t quantityAmount = quantity.amount / 10000;
		uint64_t mining_amount  = 0;
		time updatetime = now();
		auto itr = other_table.begin();
		uint8_t isInsert = false;
		if(itr == other_table.end()) {
			mining_amount = quantityAmount * 10 * 10000;
			isInsert = true;
		}
		else {
			uint64_t totalMining = itr->mining.amount / 10000;
			uint64_t rab = 10;
			if( totalMining > 0 and totalMining <= 10000000 ) {
				rab = 10;
			}
			else if( totalMining >= 10010000 and totalMining <= 20000000 ) {
				rab = 7;
			}
			else if( totalMining >= 20010000 and totalMining <= 30000000 ) {
				rab = 5;
			}
			else if( totalMining >= 30010000 and totalMining <= 40000000 ) {
				rab = 4;
			}
			else if( totalMining >= 40010000 and totalMining <= 50000000 ) {
				rab = 3;
			}
			else if( totalMining >= 50010000 and totalMining <= 60000000 ) {
				rab = 2;
			}
			else if( totalMining >= 60010000 and totalMining <= 70000000 ) {
				rab = 1;
			}
			else if( totalMining >= 70010000 and totalMining <= 80000000 ) {
				rab = 100000;
			}

			if( rab == 100000 ) {
				mining_amount = (quantityAmount * 10000) / 2;
			}
			else {
				mining_amount = quantityAmount * rab * 10000;
			}
		}

		//判断RAB是否都已经被挖出，如果没有，则转账
		auto rab_balance = eosio::token(config::tokenContract).get_balance(_self, symbol_type(S(4,RAB)).name());
		if( rab_balance.amount >= (100 * 10000) ) {
			if( isInsert ) {
				other_table.emplace( _self, [&](auto &result ) {
					extended_asset miningRab( mining_amount,S(4,RAB) );
					result.id         = other_table.available_primary_key();
					result.mining     = miningRab;
					result.updatetime = updatetime;
				});
			}
			else {
				other_table.modify( itr, _self, [&](auto &result ) {
					extended_asset miningRab( itr->mining.amount + mining_amount,S(4,RAB) );
					result.mining     = miningRab;
					result.updatetime = updatetime;
				});
			}

			extended_asset miningRabSend( mining_amount,S(4,RAB) );
			issue( getUserName(userid), miningRabSend, "Mining reward" );
		}
	}

	//募集EOS
	void rainbow::execRaise( account_name name, asset quantity, uint64_t tran_id ) {
		eosio_assert( quantity.symbol == S(4,EOS), "104" );
		//最低募集金额为1EOS且必须为1的倍数
		eosio_assert( quantity.amount % 10000 == 0 , "105" );

		uint64_t userid = getUserId( name );
		eosio_assert( userid > 0, "1000" );

		asset raise_amount;
		
		auto itr = raise_table.begin();
		asset token_amount;
		if( itr == raise_table.end() ) {
			extended_asset total_tmp( 0,S(4,RAB) );
			token_amount = getExchangeRab( quantity, total_tmp );
			raise_amount = quantity;
		}
		else {
			auto itr = raise_table.end();
			itr--;
			token_amount = getExchangeRab( quantity, itr->raise_amount );
			extended_asset tmpEos( itr->raise_amount.amount + quantity.amount,S(4,EOS) );
			raise_amount = tmpEos;
		}
		eosio_assert( (quantity.amount + raise_amount.amount) < config::totalCraise, "107" );

		uint64_t raise_id = 0;
		raise_table.emplace( _self, [&](auto &result ) {
			result.id         	= raise_table.available_primary_key();
			result.userid       = userid;
			result.eos_amount 	= quantity;
			result.token_amount = token_amount;
			result.raise_amount = raise_amount;
			result.createtime   = now();

			if( result.id == 0 ) {
				result.id = 1;
			}
			raise_id = result.id;
		});

		auto other_itr = other_table.begin();

		eosio_assert( other_itr != other_table.end(), "error" );

		other_table.modify( other_itr, _self, [&](auto &result ) {
			extended_asset raiserab( other_itr->raiserab.amount + token_amount.amount,S(4,RAB) );
			extended_asset raiseeos( other_itr->raiseeos.amount + quantity.amount,S(4,EOS) );
			result.raiserab = raiserab;
			result.raiseeos = raiseeos;
			result.updatetime = now();
        });

		issue( name, token_amount, "Raising rewards" );
	}

	//累加用户EOS下注金额
	void rainbow::cumulativebet( uint64_t userid, asset quantity, account_name from ) {
		auto itr = user_table.find( userid );
		uint64_t totalbetamount  = itr->totalbet.amount + quantity.amount;		//总花费金额
		uint64_t rewarded_amount = itr->rewarded_amount.amount;					//已结算过的金额

		uint64_t returnAmount = (uint64_t)0;
		//每下注满1000EOS，返还1%
		if( (totalbetamount - rewarded_amount) >= (config::rewardAmount * 10000) ) {
			returnAmount = config::rewardAmount * 10000;
			extended_asset returnEos( ((config::rewardAmount * 10000) / 100) * config::rewardPercent,S(4,EOS) );
			issue( getUserName(userid), returnEos, "Bet on 1000EOS rebate");
		}

		user_table.modify( itr, _self, [&](auto &result ) {
			extended_asset totalbet( totalbetamount,S(4,EOS) );
			result.totalbet = totalbet;
			extended_asset rewarded_amount( itr->rewarded_amount.amount + returnAmount,S(4,EOS) );
			result.rewarded_amount = rewarded_amount;
		});
	}

	//玩家下注时奖励给推荐人的金额。
	void rainbow::betReward( uint64_t referrer_id, account_name from, asset quantity ) {
		auto referrer_itr = user_table.find( referrer_id );
		uint64_t referrer_amount = quantity.amount / (uint64_t)1000;
		extended_asset referrerEos( referrer_amount,S(4,EOS) );
		std::string from_name = name{from}.to_string();
		issue( referrer_itr->name, referrerEos, "ref|" + std::to_string(now()) + "|" + std::to_string(referrer_id) + "|" + from_name);
	}

	uint64_t rainbow::addUser( account_name name, account_name referrer ) {
		uint64_t userid = 0;
		if( referrer != _self ) {
			login( referrer, _self );
			userid = login( name, referrer );
		}
		else {
			userid = login( name, _self );
		}

		return userid;
	}
	
	//转账
	void rainbow::transfer( account_name from, account_name to, asset quantity, string memo ) {
		require_auth( from );
		uint64_t type = 0;
		if( to != from and (to == _self or from == _self) and (quantity.symbol == S(4,RAB) or quantity.symbol == S(4,EOS)) ) {
			uint64_t pid = 0;
			bool canSave = false;
			uint64_t gameid = 0;
			if( to == _self ) {
				vector<std::string> data = tools::split( memo, "-" );				
				std::string action = data[0];
				uint64_t userid;
				if( action == "bet" or action == "t1" or action == "t2" ) {
					check();
				}

				if( action == "bet" ) {
					uint32_t gametype = (uint32_t)std::stoi( data[1] );
					uint8_t  digital  = (uint8_t)std::stoi( data[2] );
					
					if( data.size() == 4 ) {
						userid = addUser( from, string_to_name( data[3].data() ) );
					}
					else {
						userid = addUser( from, _self );
					}

					if( gametype == 1 ) {
						uint32_t game = car->getGame( quantity );

						eosio_assert( game > 0, "112" );

						uint64_t* gameInfo = car->getNum( game );
						gameid = gameInfo[0];
						car->bet( gameid, game, gameInfo[1], userid, from, quantity, digital );
						save_transfer( from, to, quantity, memo, 3, std::to_string( gameid ) );
						canSave = false;
					}
				}
				else if( action == "t1" ) {
					if( data.size() == 2 ) {
						addUser( from, string_to_name( data[1].data() ) );
					}
					else {
						addUser( from, _self );
					}

					//募集EOS，换RAB
					eosio_assert( isCanRaise() == true, "103" );
					canSave = true;
					type = 1;
				}
				else if ( action == "t2" and quantity.symbol == S(4,RAB) ) {
					if( data.size() == 2 ) {
						addUser( from, string_to_name( data[1].data() ) );
					}
					else {
						addUser( from, _self );
					}

					//质押RAB
					canSave = true;
					type = 2;
				}
			}

			if( from == _self ) {
				canSave = false;

				vector<std::string> data = tools::split( memo, "|" );
				if( data.size() == 2 ) {
					check();
					type = std::stoi( data[1] );
				}
			}
			
			if( canSave ) {
				save_transfer( from, to, quantity, memo, type, "" );
			}
		}
	}

	//保存交易信息
	void rainbow::save_transfer( account_name from, account_name to, asset quantity, string memo, uint64_t type, std::string gameinfo ) {
		uint64_t fromId = getUserId( from );
		uint64_t toId   = getUserId( to );
		uint64_t tran_id = 0;
		if( (from == _self and toId > 0) or (fromId > 0 and to == _self) ) {
			uint64_t confirm = 0;
			if( from == _self ) {
				confirm = 1;
			}

			tranlist_table.emplace( _self, [&](auto &result ) {
				result.id         = tranlist_table.available_primary_key();
				result.from       = fromId;
				result.to         = toId;
				result.quantity   = quantity;
				result.confirm    = confirm;
				result.txid       = tools::getTxid();
				result.memo       = memo;
				result.type       = type;
				result.gameinfo   = gameinfo;
				result.createtime = now();

				if( result.id == 0 ) {
					result.id = 1;
				}

				if( from != _self ) {
					result.fromname = from;
				}

				tran_id = result.id;
			});
		}
	}

	//执行确认交易后的业务逻辑
	void rainbow::runTransaction( uint64_t tran_id ) {
		auto itr = tranlist_table.find( tran_id );
		eosio_assert( itr != tranlist_table.end(), "error" );
		eosio_assert( itr->confirm == 0, "108" );
		uint64_t from_id  = itr->from;

		auto user_itr = user_table.find( from_id );
		eosio_assert( user_itr != user_table.end(), "1000" );
		
		std::string memo  = itr->memo;
		account_name from = user_itr->name;
		asset quantity    = itr->quantity;
		
		vector<std::string> data = tools::split( memo, "-" );
		std::string action = data[0];
		print(action);
		if( action == "t1" ) {
			execRaise( from, quantity, tran_id );
		}
		else if( action == "t2" and quantity.symbol == S(4,RAB) ) {
			//质押RAB
			user_table.modify( user_itr, _self, [&](auto &res ) {
				extended_asset pledgerab( user_itr->pledgerab.amount + quantity.amount,S(4,RAB) );
				res.pledgerab = pledgerab;
			});
		}
		else if( action == "bet" ) {
			uint32_t gametype = (uint32_t)std::stoi( data[1] );
			uint32_t digital  = (uint32_t)std::stoi( data[2] );

			if( gametype == 1 ) {
				if( quantity.symbol == S(4,EOS) ) {
					uint64_t gameid   = (uint64_t)std::stoi( itr->gameinfo );
					//获得奖励金额，并执行转账
					asset bonusEos = car->transfer( gameid, from_id, digital );
					if( bonusEos.amount > 0 ) {
						issue( from, bonusEos, "Racing game prize" );
					}

					//累加EOS下注总金额
					cumulativebet( from_id, quantity, from );

					//挖矿奖励
					mining( quantity, from_id );
					uint64_t referrer_id = user_itr->referrer_id;
					if( referrer_id > 0 ) {
						//推荐人奖励下注金额0.1%EOS
						betReward( referrer_id, from, quantity );
					}
					
					addDividend( quantity );		//计算并添加到分红池
				}
			}
		}

		auto delTran_itr = tranlist_table.find( tran_id );
		if( delTran_itr !=  tranlist_table.end() ) {
			tranlist_table.erase( delTran_itr );		//删除相关交易数据，减少RAM消耗
		}
	}

	//确认交易，并执行后续流程;如果交易异常，则记录
	void rainbow::checktx( uint64_t tran_id, uint8_t type ) {
		require_auth( _self );
		if( type == 1 ) {
			//正常的交易
			runTransaction( tran_id );
		}
		else if( type == 2 ) {
			//异常的交易
			auto tranlist_itr = tranlist_table.find( tran_id );
			eosio_assert( tranlist_itr != tranlist_table.end(), "1001" );
			eosio_assert( tranlist_itr->confirm == 0, "108" );
			uint64_t from = tranlist_itr->from;
			if( from > 0 ) {
				auto user_itr = user_table.find( from );
				uint16_t newFailCount = user_itr->failcount + 1;
				if( newFailCount >= 3 ) {
					tranlist_table.erase( tranlist_itr );		//删除相关交易数据，减少RAM消耗
				}

				user_table.modify( user_itr, _self, [&](auto &result ) {
					result.failcount = newFailCount;
				});
			}
		}
	}
}

#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
	void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
		auto self = receiver; \
		if( action == N(onerror) ) { \
			/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
			eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
		} \
		if( ((code == self && action != N(transfer) ) || (code == N(eosio.token) && action == N(transfer)) || (code == eosio::config::tokenContract && action == N(transfer)) || action == N(onerror)) ) { \
			TYPE thiscontract( self ); \
			switch( action ) { \
				EOSIO_API( TYPE, MEMBERS ) \
			} \
		/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
		} \
	} \
}

EOSIO_ABI_EX( eosio::rainbow, (setup)(transfer)(checktx) )